home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Pascal Super Library
/
Pascal Super Library (CW International)(1997).bin
/
PGM_TOOL
/
TCUNIT
/
STATIC.TXT
< prev
next >
Wrap
Text File
|
1989-07-02
|
15KB
|
345 lines
Article text, originally published as "Demagnetizing Static
Variables," Programmer's Journal 7.1, 1989, pp 42-52.
-----
Copyright (c) 1989 by Tom Swan. All rights reserved. Limited
permission granted to use programming in compiled form only.
-----
Author's note: This information and the accompanying programs are
compatible with Turbo Pascal 5.5. I wrote the article before this
version was available, so any references to page numbers in the
Borland references may be incorrect.
Demagnetizing Static Variables
by Tom Swan
"Modifying typed constants (alias static variables) stored in
Turbo Pascal 4.0 and 5.0 compiled EXE code files is hairy
business--but it can be done if you're careful."
Static variables, or typed constants as Turbo Pascal calls them,
stick to compiled code like magnets to iron. Declared as
constants but compiled as variables, typed constants are stored
on disk inside a program's EXE code file. Because typed
constants have predefined values, they eliminate wasteful
initializations of global variables at run time. Despite this
advantage, however, after compiling, there's no easy way to
change a program's typed constants--for example, to let people
make keyboard assignments, select window colors, and modify other
default settings.
A telephone call from a reader prompted me to think of
several possible solutions to this problem. I knew I'd have to
locate the typed constants embedded in the compiled code file and
modify only the necessary bytes, storing the result back on disk
with all other bytes intact. Mucking around with EXE disk files
gives me the heebie jeebies, so the code would have to be
reasonably immune to changes in future compiler versions. The
program should run fast, be easy to modify, work with both small
and large applications, wash the floor, dust the shelves, and
take out the garbage.
The following describes the results of my experiments,
largely successful although I'm still doing the floor, shelves,
and garbage by hand. After a brief refresher course on typed
constants as stored on disk and in memory, I'll explain how to
use the accompanying library unit to write custom installation
programs. Also included are demonstration programs that you can
use as starting places for your own projects.
A Needle in a Byte Stack
Finding typed constants in memory and on disk isn't nearly as
difficult as finding a needle in you know what. Let's start with
values in memory, as these are the easiest to locate. The first
typed constant in memory is always stored at offset zero in the
data segment. (There is only one data segment in a Turbo Pascal
program.) Suppose you have these declarations:
const
aword : word = ( 1024 );
achar : char = ( 'X' );
abyte : byte = ( 15 );
The typed constant aword is stored at DSeg:0000, occupying
two bytes. After this comes achar at DSeg:0002. Next, abyte
follows at DSeg:0003. DSeg is a predeclared constant equal to
the value of register DS. Rather than calculate these offsets
manually, use the Ofs function to find the address of any typed
constant. For example, this displays the offset addresses of the
three typed constants in memory:
writeln(
' aword=', ofs(aword),
' achar=', ofs(achar),
' abyte=', ofs(abyte) );
In Turbo Pascal 4.0, typed constant values follow each other
with no wasted bytes between. As a result, lone byte values can
lead to extra CPU machine cycles by forcing words to begin at odd
addresses. If you insert a fourth typed constant word between
achar and abyte, the new value resides at DSeg:0003, making the
CPU work harder to access the value. To fix this problem, Turbo
Pascal 5.0 provides a new switch {$A+} to align typed constants
and other variables on even addresses, letting the CPU access 16-
bit quantities as quickly as possible. (Word alignment offers no
advantage on PCs with 8088 processors.) The default is {$A-} for
no alignment.
We Are Not Alone
Your typed constants are not alone in memory. The System unit,
containing Turbo Pascal's runtime library and linked into every
compiled program, declares its own set of typed constants. (The
5.0 Reference Guide lists these on page 125.) In memory, items
in the data segment follow this order:
1. Program typed constants
2. Unit typed constants
3. System typed constants
4. Global variables
The main program's typed constants are always first,
beginning at offset zero. Next come any typed constants declared
in units that the program lists in a USES declaration. Last come
the System unit's typed constants, followed by the program's
global variables. Together, these four items make up the
program's data segment, which can be as large as 64K. Items 1-3
are stored in the compiled EXE code file. Memory for global
variables (item 4) is allocated at run time--global variables are
never stored in the disk code file.
Smart Linker; Dumb Programmer
Turbo Pascal's smart and crafty linker, which attempts to
optimize compiled code, requires you to refer to at least one
typed constant declared in your program and in all units. If you
don't, the linker realizes you aren't using the typed constants
and does not reserve space for them in the code! While writing
codeless test programs to determine where typed constants are
stored, it took me longer than I care to reveal to realize the
linker was "helping" me by throwing my constants away. The
following short program demonstrates how Turbo's smart aleck
linker can get you into trouble as it did me:
program test;
const
atc:char=('a');
begin
writeln( atc );
writeln(
char( ptr(dseg,0 )^) )
end.
The program declares one typed constant, atc. Two writeln
statements display atc's value, the letter 'a'. The first
writeln refers to atc by name. The second refers to atc the hard
way, dereferencing a pointer to DSeg:0000 and recasting as type
Char, admittedly the long way around a simple job. Take out the
first writeln statement and guess what happens. The second
writeln no longer displays 'a'! Turbo Pascal throws out atc
because it doesn't realize that the ptr expression refers to the
typed constant.
Obviously, you won't usually refer to typed constants this
way, but you might write a program that expects another module to
reference a typed-constant array or large record at DSeg:0000.
Don't do this. Turbo Pascal does not save typed constants unless
you refer by name to at least one in each module. The same rule
goes for typed constants in units. Either the unit code or
another module must refer to at least one typed constant by name
or the linker throws these babies out with the bathwater.
Sniffing for Typed Constants
Turbo Pascal stores typed constants as the last bytes in a disk
code file. At run time, the bytes are copied to the start of the
program's data segment in memory. On disk and in memory, the
bytes are paragraph aligned--that is, beginning at an offset in
the code file and at an address in memory evenly divisible by 16.
The problem of writing an installation program to modify
default values in compiled disk code files requires finding the
offset to the first byte of the first typed constant. Listing 1
(WINDGLOB.PAS) makes this easy by declaring a marker CBase
assigned the string value '@CBASE@'. The program's default
settings follow CBase--in this sample, the typed constants
WBForeColor to WTitle. Last, a dummy typed constant EBase marks
the end of the typed constants area. Ebase's data type and value
are unimportant, but CBase must be a string.
Because the application and installation program refer to
the same values, it's best to declare all typed constants in a
separate unit similar to WINDGLOB.PAS. In this example, the unit
has no code, although it certainly could.
The next step is to write the main application, WINDTEST.PAS
in Listing 2. This program lists WINDGLOB in a USES declaration,
importing the typed constants from Listing 1. Run WINDTEST to
display a simple window with scrolling random characters. Press
any key to end the program.
The last job is to write the installation program, which
modifies the typed constant default values in the compiled
WINDTEST.EXE program. Listing 3 (WINDINST.PAS) displays a menu
to let you change window colors and type a new window title
string. The next section describes how to compile and run the
programs.
Compiling The Test Programs
First compile Listing 4, TCUNIT.PAS, creating TCUNIT.TPU. (With
the integrated Turbo Pascal compiler, be sure the Compile menu's
Destination option is set to "Disk.") TCUNIT has procedures to
help you write your own installation programs and contains a
function to locate and modify typed constant values in compiled
EXE files. I'll explain how to use the unit in a moment.
After compiling TCUNIT, compile WINDGLOB.PAS to
WINDGLOB.TPU. Then, compile WINDTEST.PAS and run the test
application. Finally, compile WINDINST.PAS and run. Change any
of the values listed in the menu. In this no-frills version, you
have to specify colors by number even though WINDGLOB.PAS
declares the default colors by name, using identifiers declared
in the standard Crt unit. Press Q to quit and modify
WINDTEST.EXE. Press X to quit and not save changes. Run
WINDTEST again to see the effect of your new values.
How TCUnit Works
Two procedures in TCUNIT, GetWord and GetStr, prompt for word and
string values. WINDINST calls these procedures to let you enter
new values for various typed constant values. You'll probably
want to add your own procedures to prompt for values of other
types: such as GetInteger, GetChar, GetReal, and so forth. Use
the two procedures listed here as guides.
Function ChangesSaved in TCUNIT does all the work of opening
the compiled EXE code file, locating the typed constants, and
writing the modified values back to disk. See the end of
WINDINST for an example call to this function, which requires a
file name, the CBase marker string, and the offset addresses of
the CBase and EBase typed constants. ChangesSaved performs
several operations:
* First the EXE file is opened. Parameter fileName must be the
name of a compiled EXE file with CBase and EBase typed constants
around the values to be modified, as shown in WINDGLOB.PAS.
* Next, function FoundCBase returns true if the CBase marker
string is found in the EXE code file. If so, FoundCBase returns
the byte offset to this string. If not, FoundCBase returns
false, causing an error message to be displayed.
* If CBase is found, procedure SaveData copies the in-memory
typed constants to the compiled EXE code file, overwriting only
the bytes from CBase to the byte just before EBase. No other
bytes are changed in the disk code file.
For all this to work correctly, you must be sure to use the
identical typed constants in both the main (WINDTEST) and
installation (WINDINST) programs. The safest bet is to declare
all typed constants in one separate unit (WINDGLOB) and then use
the unit in both programs.
Writing Your Own Installers
Even without fully understanding every detail about how TCUnit
operates, it's easy to write your own installation programs.
Just follow these steps:
1. You don't have to modify TCUnit, although you might want to
add procedures to prompt for specific data types as mentioned
earlier.
2. Store all typed constants in a separate unit and compile.
Start with CBase and end with EBase as in WINDGLOB.PAS. The
number, type, names, and values of typed constants in between
CBase and EBase are completely up to you.
3. Write your application, using the unit of typed constants from
step 2. Compile to disk.
4. Write your installation program, using WINDINST.PAS as a
guide. Add TCUNIT and your typed constants unit to the program's
USES statement. Call ChangesSaved to write modified values to
your program's code file.
By the way, you can also use WINDINST to modify its own code
file, WINDINST.EXE. Doing this displays the modified settings
the next time you run the installation program. Also, the CBase
marker string (see WINDGLOB.PAS) can be anything you like--it
doesn't have to be '@CBASE@' as it is here. The string must be
unique, occurring nowhere else in the code file. You might
change CBase to your name or copyright. Then, if someone tries
to remove your notice from the code file, the installation
program will no longer work. Sneaky, no?
Improving the Programs
When you run these tests, you'll notice that WINDINST pauses for
several seconds while hunting for CBase in the compiled code.
There's a simple way to reduce this time. First, remove the (*
and *) comment brackets from the two writeln statements inside
TCUNIT's FoundCBase function. Recompile WINDINST and press Q.
Jot down the reported offset. When I did this, I got 4768,
although your number is likely to be different. The value is
decimal, not hex.
Edit TCUNIT.PAS again and replace the comment brackets in
FoundCBase. Then change the double IF statement in ChangesSaved
near the end of the program to read:
IF err=0 THEN
SaveData(f,4768,...)
In other words, replace variable offset with the value you
noted earlier and remove the call to FoundCBase. Now when you
compile and run WINDINST, the disk update takes place almost
instantaneously as TCUNIT no longer has to hunt for the offset to
CBase.
Perform this step only after compiling your main program for
the last time. (Is there ever a last time?) Any changes to your
typed constants or to the program require you to recalculate the
offset in the code file. Using the wrong offset is almost sure
to lead to a crash.
Although these ideas and test programs are experimental, I'm
planning to use them as the basis for an installation program in
my Turbo Pascal database system as well as in other programs. In
the past, I've found many uses for typed constants, but I'm
planning to work them in more frequently, especially now that I
have a way to demagnetize their previously all too static values.
Listing 1: WINDGLOB.PAS
Listing 2: WINDTEST.PAS
Listing 3: WINDINST.PAS
Listing 4: TCUNIT.PAS
###
About the author...
Tom Swan writes the monthly PC World columns Star-Dot-Star and
Developer's Toolbox. He is a contributing editor to PC World and
Programmer's Journal, and is the author of Mastering Turbo Assembler
(1989) and Mastering Turbo Pascal 5.5 (Sep 1989) published by Howard
W. Sams, Indianapolis. You can contact the author at:
Swan Software
P. O. Box 206
Lititz, PA 17543
(717) 627-1911
Compuserve ID: 73627,3241
MCI Mail handle: TSWAN